package com.springboot.jdk.lock;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * 锁优化案例实战
 *
 * @author: zr
 * @date: 2021-10-04
 */
public class RedisLockDemo {

    public static void main(String[] args) {
        long goodsSkuId = 1L;
        long purchaseCount = 60L;
        int stockSegmentSeq = new Random().nextInt(10) + 1;
        InventoryDAO inventoryDAO = new InventoryDAO();

        // 库存分段加锁
        RLock rLock = new RLock("stock_" + goodsSkuId + "_" + stockSegmentSeq);

        // 添加锁
        rLock.lock();
        // 查询库存
        Long stock = inventoryDAO.getStockByGoodsSkuId(goodsSkuId, stockSegmentSeq);

        // 如果你查出某个段的库存为0的话，然后你就应该给去遍历所有的段
        // 如果不为0 ，就退出来
        if (stock == 0L) {
            // 如果这个分段库存为0，就释放掉分段库存锁
            rLock.unlock();

            boolean findOtherStockSegment;

            for (int i = 1; i < 10; i++) {
                if (i == stockSegmentSeq) {
                    continue;
                }

                rLock = new RLock("stock_" + goodsSkuId + "_" + i);
                rLock.lock();

                stock = inventoryDAO.getStockByGoodsSkuId(goodsSkuId, stockSegmentSeq);
                if (stock != 0) {
                    stockSegmentSeq = i;
                    findOtherStockSegment = true;
                    break;
                } else {
                    rLock.unlock();
                }
            }
        }

        if (stock == 0) {
            System.out.println("库存不足");
            return;
        }

        if (stock > purchaseCount) {
            // 扣减库存
            inventoryDAO.updateStock(goodsSkuId, stockSegmentSeq, stock - purchaseCount);
            rLock.unlock();

        }

        // 代码走到这里，就证明说，当前这个分段的库存小于要购买的数量，此时就要合并分段加锁
        long totalStock = stock;

        Map<RLock, StockDto> otherLocks = new HashMap<>();
        StockDto stockDto = new StockDto(stockSegmentSeq, stock);
        otherLocks.put(rLock, stockDto);

        for (int i = 1; i < 10; i++) {
            if (i == stockSegmentSeq) {
                continue;
            }

            RLock otherLockRLock = new RLock("stock_" + goodsSkuId + "_" + i);
            otherLockRLock.lock();

            // 获取库存
            Long otherStock = inventoryDAO.getStockByGoodsSkuId(goodsSkuId, i);
            // 如果当前库存分段为0， 释放锁，跳过
            if (otherStock == 0) {
                otherLockRLock.unlock();
                continue;
            }

            // 如果当前库存分段不为0的话， 就累加totalStock
            totalStock += otherStock;
            StockDto otherStockDto = new StockDto(i, otherStock);
            otherLocks.put(otherLockRLock, otherStockDto);
            if (totalStock >= purchaseCount) {
                break;
            }
        }

        // 尝试过所有的其他分段库存之后，还是无法满足购买数量，就 提示 库存不足
        if (totalStock < purchaseCount) {
            System.out.println("库存不足，订单创建失败");
            // 如果库存不足，就将所有获取到的分库库存锁释放掉
            for (RLock key : otherLocks.keySet()) {
                key.unlock();
            }
            return;
        }

        // 剩余需要购买库存的数量
        long remainPurchaseStock = purchaseCount;

        // 依次进行分段加锁的库存数量扣减
        for (Map.Entry<RLock, StockDto> entry : otherLocks.entrySet()) {
            RLock lock = entry.getKey();
            StockDto otherStockDto = entry.getValue();
            long otherStock = otherStockDto.getStock();

            // 判断 当前分段库存数量 是否 小于剩余扣减的库存数量
            // 1. 如果小于，就将当前 分段库存 库存数量更新为0，并是释放锁，设置剩余 购买数量
            // 2、如果大于，就将剩余 库存数量 设置为0，更新 当前 分段库存 库存数量为 当前 分段库存数量 - 剩余 需要购买 库存数量， 释放锁
            if (otherStock < remainPurchaseStock) {
                remainPurchaseStock = remainPurchaseStock - otherStock;
                inventoryDAO.updateStock(goodsSkuId, otherStockDto.getStockSegmentSeq(), 0L);
                lock.unlock();
            } else {
                inventoryDAO.updateStock(goodsSkuId, otherStockDto.getStockSegmentSeq(), otherStock - remainPurchaseStock);
                lock.unlock();
                remainPurchaseStock = 0;
            }
        }
    }

    static class InventoryDAO {

        public Long getStockByGoodsSkuId(Long goodsSkuId, int stockSegmentSeq) {
            return 999L;
        }

        public void updateStock(Long goodsSkuId, int stockSegmentSeq, Long stock) {
            System.out.println("更新商品库存， goodsSkuId:" + goodsSkuId + ", goodsStock:" + stock);
        }
    }

    static class RLock {

        private String name;

        public RLock() {
        }

        public RLock(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void lock() {
            System.out.println("添加锁：" + name);
        }

        public void unlock() {
            System.out.println("释放锁：" + name);
        }
    }

    static class StockDto {
        /**
         * 库存分段位置
         */
        private int stockSegmentSeq;

        /**
         * 库存数量
         */
        private long stock;

        public StockDto() {
        }

        public StockDto(int stockSegmentSeq, long stock) {
            this.stockSegmentSeq = stockSegmentSeq;
            this.stock = stock;
        }

        public int getStockSegmentSeq() {
            return stockSegmentSeq;
        }

        public void setStockSegmentSeq(int stockSegmentSeq) {
            this.stockSegmentSeq = stockSegmentSeq;
        }

        public long getStock() {
            return stock;
        }

        public void setStock(long stock) {
            this.stock = stock;
        }
    }
}
